﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using Ewk.MachineLearning.Persistance.Domain;
using Ewk.MachineLearning.Persistance.Interfaces;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Linq;

namespace Ewk.MachineLearning.Persistance.DataAccess
{
    class Catalog : ICatalog
    {
        static Catalog()
        {
            ClassMapper.RegisterMaps();
        }

        private MongoDatabase _database;

        public Catalog()
        {
            var dbUrl = ConfigurationManager.AppSettings["MONGOLAB_URI"] ?? "mongodb://localhost/MachineLearningDb";
            var connectionString = string.Format(CultureInfo.InvariantCulture, "{0}?safe=true", dbUrl);

            _database = MongoDatabase.Create(connectionString); // TODO: remove usage of obsolete method

            // Set an index on Word.Content
            var mongoIndexKeys = IndexKeys<Word>.Ascending(word => word.Content);
            if (!GetMongoCollection<Word>().IndexExists(mongoIndexKeys))
            {
                GetMongoCollection<Word>().EnsureIndex(
                    mongoIndexKeys,
                    IndexOptions.SetUnique(true).SetBackground(true));
/*
                GetMongoCollection<Word>().DropIndex(mongoIndexKeys);
*/
            }
        }

        #region Implementation of ICatalog

        public IQueryable<T> Entities<T>() where T : Entity
        {
            return GetMongoCollection<T>()
                .AsQueryable();
        }

        public virtual T Add<T>(T entity) where T : Entity
        {
            entity.ModificationDate = DateTime.UtcNow;

            var collection = GetMongoCollection<T>();
            var result = collection.Insert(entity);

            return entity; // BsonSerializer.Deserialize<T>(result.Response);
        }

        public virtual IEnumerable<T> Add<T>(IEnumerable<T> entities) where T : Entity
        {
            entities = entities.ToList();

            foreach (var entity in entities)
            {
                entity.ModificationDate = DateTime.UtcNow;
            }

            var collection = GetMongoCollection<T>();
            var results = collection.InsertBatch(entities);

            return entities; /*results
                .Select(result =>
                        BsonSerializer.Deserialize<T>(result.Response));*/
        }

        public virtual T Update<T>(T entity) where T : Entity
        {
            entity.ModificationDate = DateTime.UtcNow;

            var collection = GetMongoCollection<T>();
            var result = collection.Save(entity);

            return entity; // BsonSerializer.Deserialize<T>(result.Response);
        }

        public virtual void Remove<T>(T entity) where T : Entity
        {
            var query = Query<T>.EQ(arg => arg.Id, entity.Id);
            var collection = GetMongoCollection<T>();
            var result = collection.Remove(query, RemoveFlags.Single);
        }

        public void RemoveAll<T>() where T : Entity
        {
            var collection = GetMongoCollection<T>();
            var result = collection.RemoveAll();
        }

        #endregion

        #region Implementation of IDisposable

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // The bulk of the clean-up code is implemented in Dispose(bool)
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
            }

            // free native resources if there are any.
            if (_database != null)
            {
                _database.Server.Disconnect();
                _database = null;
            }
        }

        #endregion

        private MongoCollection<T> GetMongoCollection<T>() where T : Entity
        {
            return _database.GetCollection<T>(typeof(T).Name);
        }
    }
}